package com.gitee.l0km.codegen.thrift;

import static com.google.common.base.Preconditions.*;

import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;

/**
 * 定义thrift协议cxx类型
 * @author guyadong
 *
 */
public class CxxTypeMeta {

	enum ThriftProtocolType {
	    TYPE_VOID(true,false,false),
	    TYPE_STRING(true,false,false),
	    TYPE_BOOL(true,false,false),
	    TYPE_I8(true,false,true),
	    TYPE_I16(true,false,true),
	    TYPE_I32(true,false,true),
	    TYPE_I64(true,false,true),
	    TYPE_DOUBLE(true,false,true),
	    TYPE_BINARY(true,false,false),
	    TYPE_STRUCT(false,false,false),
	    TYPE_MAP(false,true,false),
	    TYPE_SET(false,true,false),
	    TYPE_LIST(false,true,false),
	    TYPE_ENUM(false,false,false);
	    public final boolean isBaseType,isContainer,isNumber;
		private ThriftProtocolType(boolean isBaseType, boolean isContainer,boolean isNumber) {
			this.isBaseType = isBaseType;
			this.isContainer = isContainer;
			this.isNumber = isNumber;
		}
 		private Supplier<String> getTypeSupplier(CxxTypeMeta meta){
			switch(this){
		    case TYPE_MAP:
		    	return meta.mapSupplier;
		    case TYPE_SET:
		    	return meta.setSupplier;
		    case TYPE_LIST:
		    	return meta.listSupplier;
		    default:
		    	return meta.type;
			}
 		}
	};
    public static final CxxTypeMeta BOOL = new CxxTypeMeta(ThriftProtocolType.TYPE_BOOL, "bool");
    public static final CxxTypeMeta BYTE = new CxxTypeMeta(ThriftProtocolType.TYPE_I8, "int8_t");
    public static final CxxTypeMeta DOUBLE = new CxxTypeMeta(ThriftProtocolType.TYPE_DOUBLE, "double");
    public static final CxxTypeMeta I16 = new CxxTypeMeta(ThriftProtocolType.TYPE_I16, "int16_t");
    public static final CxxTypeMeta I32 = new CxxTypeMeta(ThriftProtocolType.TYPE_I32, "int32_t");
    public static final CxxTypeMeta I64 = new CxxTypeMeta(ThriftProtocolType.TYPE_I64, "int64_t");
    public static final CxxTypeMeta STRING = new CxxTypeMeta(ThriftProtocolType.TYPE_STRING, "std::string");
    public static final CxxTypeMeta BINARY = new CxxTypeMeta(ThriftProtocolType.TYPE_BINARY, "std::string");
    public static final CxxTypeMeta VOID = new CxxTypeMeta(ThriftProtocolType.TYPE_VOID, "void");
	private Supplier<String> arraySupplier=new Supplier<String>(){	
	@Override
	public String get() {
		StringBuilder sb = new StringBuilder();
		sb.append("std::vector<")
			.append(valueType.getType()).append(">");
		return  sb.toString();
	}};
	private Supplier<String> listSupplier=new Supplier<String>(){

		@Override
		public String get() {
			StringBuilder sb = new StringBuilder();
			sb.append("std::vector<")
				.append(valueType.getType()).append(">");
			return  sb.toString();  
		}};
	private Supplier<String> setSupplier=new Supplier<String>(){

		@Override
		public String get() {
			StringBuilder sb = new StringBuilder();
			sb.append("std::set<")
				.append(valueType.getType()).append(">");
			return  sb.toString();  
		}};
	private Supplier<String> mapSupplier = new Supplier<String>(){

		@Override
		public String get() {
	        StringBuilder sb = new StringBuilder();
			sb.append("std::map<")
				.append(keyType.getType()).append(",")
				.append(valueType.getType()).append(">");
			return sb.toString();
		}};
	private final ThriftProtocolType protocolType;
	private final CxxTypeMeta keyType;
	private final CxxTypeMeta valueType;
	private final Supplier<String> type;
	private final boolean isArray;
	private static class NameSupplier implements Supplier<String>{
		private String cxxType;

		public NameSupplier(String cxxType) {
			super();
			checkArgument(null !=cxxType, "cxxType is null");
			this.cxxType = cxxType;
		}

		@Override
		public String get() {
			return CxxHelper.getTypeName(cxxType);
		}
	}
	static CxxTypeMeta struct(final String cxxType)
    {    	
    	Supplier<String> supplier = new NameSupplier(cxxType);
        return new CxxTypeMeta(ThriftProtocolType.TYPE_STRUCT,supplier);
    }

    static CxxTypeMeta enumType(final String cxxType)
	{
    	Supplier<String> supplier = new NameSupplier(cxxType);
	    return new CxxTypeMeta(ThriftProtocolType.TYPE_ENUM,supplier);
	}

	static <K, V> CxxTypeMeta map(CxxTypeMeta keyType, CxxTypeMeta valueType)
    {
        if(null == valueType || null == valueType){
        	return null;
        }

        return new CxxTypeMeta(ThriftProtocolType.TYPE_MAP, keyType, valueType, false);
    }
	static <E> CxxTypeMeta set(CxxTypeMeta valueType)
    {
		 if(null == valueType){
	        	return null;
	     }

        return new CxxTypeMeta(ThriftProtocolType.TYPE_SET, null, valueType, false);
    }

    static <E> CxxTypeMeta list(CxxTypeMeta valueType)
    {
    	 if(null == valueType){
         	return null;
         }

        return new CxxTypeMeta(ThriftProtocolType.TYPE_LIST, null, valueType, false);
    }
    static CxxTypeMeta array(CxxTypeMeta valueType)
    {
        if(null == valueType){
        	return null;
        }
        return new CxxTypeMeta(ThriftProtocolType.TYPE_LIST, null, valueType, true);
    }
    /**
     * 容器类型构造方法<br>
     * {@code isAray}为{@code true}且{@code valueType}为BYTE时,也允许{@code protocolType}为BINARY
     * @param protocolType
     * @param keyType
     * @param valueType
     * @param isArray 
     */
    private CxxTypeMeta(ThriftProtocolType protocolType, CxxTypeMeta keyType, CxxTypeMeta valueType, boolean isArray)
    {
        checkArgument(null !=protocolType, "protocolType is null");        
        checkArgument(protocolType.equals(ThriftProtocolType.TYPE_BINARY) || protocolType.isContainer, "protocolType must be SET,MAP,LIST,BINARY");
        if(isArray){
        	checkArgument(protocolType.equals(ThriftProtocolType.TYPE_LIST) || protocolType.equals(ThriftProtocolType.TYPE_BINARY),"protocolType must be LIST or BINARY,if isArray");
        	if(protocolType.equals(ThriftProtocolType.TYPE_BINARY)){
        		checkArgument(BYTE.equals(valueType),"valueType must be BYTE,if isArray is true and protocolType is TYPE_LIST");
        	}
        }
        this.protocolType = protocolType;
        this.isArray=isArray;
        this.type = isArray? arraySupplier : protocolType.getTypeSupplier(this);
        this.keyType = keyType;
        this.valueType = checkNotNull(valueType,"valueType is null");
    }
    /**
     * 简单类型构造方法
     * @param protocolType
     * @param cxxType
     */
    private CxxTypeMeta(ThriftProtocolType protocolType, String cxxType){
    	this(protocolType,Suppliers.ofInstance(checkNotNull(cxxType, "cxxType is null")));
	}
    /**
     * 非容器类型构造方法
     * @param protocolType
     * @param cxxType
     */
    private CxxTypeMeta(ThriftProtocolType protocolType, Supplier<String> cxxType){
        checkNotNull(protocolType, "protocolType is null"); 
        checkArgument(!protocolType.isContainer, "protocolType must not be SET,MAP,LIST");        
        checkNotNull(cxxType, "cxxType is null");        
        this.protocolType = protocolType;
        this.isArray = false;
        this.type = cxxType;
        this.keyType = null;
        this.valueType = null;
	}
	public String getType(){
		return type.get();
	}
	public String getSampleType(){
		if(protocolType==ThriftProtocolType.TYPE_STRUCT || protocolType==ThriftProtocolType.TYPE_ENUM){
			return CxxHelper.simpleName(type.get());
		}
		return type.get();
	}
	public String getFullType(){
		if(type instanceof NameSupplier){
			return ((NameSupplier)type).cxxType;
		}else{
			return type.get();
		}
	}
	public String getNamespace(){
		if(type instanceof NameSupplier){
			return CxxHelper.getCxxNamespace(((NameSupplier)type).cxxType);
		}else{
			return CxxHelper.getCxxNamespace(type.get());
		}
	}
	public CxxTypeMeta getKeyType() {
		return keyType;
	}
	public CxxTypeMeta getValueType() {
		return valueType;
	}

	public ThriftProtocolType getProtocolType() {
		return protocolType;
	}
	public boolean isBool(){
		return ThriftProtocolType.TYPE_BOOL.equals(getProtocolType());
	}
	public boolean isVoid(){
		return ThriftProtocolType.TYPE_VOID.equals(getProtocolType());
	}
	public boolean isString(){
		return ThriftProtocolType.TYPE_STRING.equals(getProtocolType());
	}
	public boolean isBinary(){
		return ThriftProtocolType.TYPE_BINARY.equals(getProtocolType());
	}
	public boolean isMap() {
		return ThriftProtocolType.TYPE_MAP.equals(getProtocolType());
	}
	public boolean isSet() {
		return ThriftProtocolType.TYPE_SET.equals(getProtocolType());
	}
	public boolean isList() {
		return ThriftProtocolType.TYPE_LIST.equals(getProtocolType());
	}
	public boolean isEnum() {
		return ThriftProtocolType.TYPE_ENUM.equals(getProtocolType());
	}
	public boolean isStruct() {
		return ThriftProtocolType.TYPE_STRUCT.equals(getProtocolType());
	}
	public boolean isContainer(){
		return this.getProtocolType().isContainer;
	}
	public boolean isBaseType(){
		return this.getProtocolType().isBaseType;
	}
	
	public boolean isNumber(){
		return getProtocolType().isNumber;
	}
	public boolean isArray() {
		return isArray;
	}

	public boolean isByValue(){
		return isBool() || isNumber() || isEnum();
	}
	public boolean isByReference(){
		return !isByValue() && !isVoid();
	}
	public boolean isCanMove(){
		return isBinary() || isString() || isContainer() || isStruct();
	}
	/**
	 * 返回对应的cpp参数类型<br>
	 * 如果是传引用和可移动参数，则加 'const' 前缀 和 引用后缀 '&'
	 * @since 2.3.5
	 */
	public String getParameterType(){
		if(isCanMove() && isByReference()) {
			return "const " + getType() + " &";
		}
		return getType();
	}
	/**
	 * 类名转换，不含namespace
	 * @param name
	 */
	public CxxTypeMeta castName(String name){
		checkArgument(null !=name, "ns is null");
		checkArgument(isEnum() || isStruct(),"only ENUM or STRUCT can be cast namespace");
        return new CxxTypeMeta(protocolType,getNamespace() + "::" + name);
	}
	/**
	 * 类型转换
	 * @param cxxType
	 */
	public CxxTypeMeta cast(String cxxType){
		checkArgument(null !=cxxType, "cxxType is null");
        return new CxxTypeMeta(protocolType,cxxType);
	}
	/**
	 * 类型转换
	 * @param keyType
	 * @param valueType
	 * @param isArray
	 */
	public CxxTypeMeta cast(CxxTypeMeta keyType, CxxTypeMeta valueType, boolean isArray){
		return new CxxTypeMeta(protocolType,keyType,valueType,isArray);
	}
@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("CxxTypeMeta [protocolType=");
		builder.append(protocolType);
		builder.append(", type=");
		builder.append(type.get());
		builder.append(", isArray=");
		builder.append(isArray);
		builder.append("]");
		return builder.toString();
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((protocolType == null) ? 0 : protocolType.hashCode());
		result = prime * result + ((type == null) ? 0 : type.get().hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		CxxTypeMeta other = (CxxTypeMeta) obj;
		if (protocolType != other.protocolType)
			return false;
		if (type == null) {
			if (other.type != null)
				return false;
		} else if (!type.get().equals(other.type.get()))
			return false;
		return true;
	}

}
